﻿using RockBus.Configuration;
using RockBus.Exceptions;
using RockBus.PipelineComponent.Interfaces;
using System;
using System.Configuration;
using System.IO;
using System.ServiceModel.Channels;
using System.Xml;
using System.Xml.Schema;

namespace RockBus.PipelineComponent.Xml
{
    public class Validator
    {
        public Validator(XmlSchemaSet compiledSchemaSet, IPipelineComponentConfiguration configuration)
        {
            this.Settings = new XmlReaderSettings();
            this.Settings.Schemas = compiledSchemaSet;
            this.Settings.ValidationEventHandler += ValidationEventHandler;
            this.Settings.ValidationType = ValidationType.Schema;
            this.Configuration = configuration;
        }

        private XmlReaderSettings Settings { get; set; }

        private IPipelineComponentConfiguration Configuration { get; set; }

        public XmlReader CreateValidatingReader(Message message, IPipelineContext pipelineContext)
        {
            var xdr = message.GetReaderAtBodyContents();
            var validatingXmlReader = XmlReader.Create(xdr.ReadSubtree(), this.Settings);
            return validatingXmlReader;
        }

        private void ValidationEventHandler(object sender, ValidationEventArgs e)
        {
            if (e.Exception != null)
            {
                var ime = new InvalidMessageException(
                    string.Format(
                        "Document validation failed in PipelineComponent {0} ({2}/{3}): {1}.",
                        this.Configuration.Id,
                        e.Message,
                        e.Exception.LineNumber,
                        e.Exception.LinePosition),
                    e.Exception);
                throw ime;
            }
        }
    }

    public class ValidationPipelineComponent : IPipelineComponent
    {
        public ValidationPipelineComponent(IPipelineComponentConfiguration configuration)
        {
            this.Configuration = configuration;
            this._compiledSchemaSet = new Lazy<XmlSchemaSet>(CreateXmlSchemaSet, true);
        }

        public IPipelineComponentConfiguration Configuration { get; private set; }

        private readonly Lazy<XmlSchemaSet> _compiledSchemaSet;

        private XmlSchemaSet CompiledSchemaSet { get { return this._compiledSchemaSet.Value; } }

        public Message Execute(Message message, IPipelineContext pipelineContext)
        {
            var v = new Validator(this.CompiledSchemaSet, this.Configuration);

            var vr = v.CreateValidatingReader(message, pipelineContext);
            var newMessage = pipelineContext.MessageFactory.CreateMessage(vr, message.Headers.Action, message);
            return newMessage;
        }

        private XmlSchemaSet CreateXmlSchemaSet()
        {
            var xss = new XmlSchemaSet();

            var xsdFileName = this.Configuration["XsdFileName"];
            if (!string.IsNullOrEmpty(xsdFileName))
            {
                xsdFileName = Path.Combine(ServiceBusConfigurationSection.Instance.Extensions.ContentFolder, xsdFileName);
                xss.Add(null, xsdFileName);
            }

            var xsdFolder = this.Configuration["XsdFolder"];
            if (!string.IsNullOrEmpty(xsdFolder))
            {
                xsdFolder = Path.Combine(ServiceBusConfigurationSection.Instance.Extensions.ContentFolder, xsdFolder);
                var xsdFileNames = Directory.EnumerateFiles(xsdFolder, "*.xsd");
                foreach (var xsdFile in xsdFileNames)
                {
                    xss.Add(null, xsdFile);
                }
            }

            if (xss.Count == 0)
            {
                throw new ConfigurationErrorsException(string.Format("Invalid ValidationPipelineComponent configuration. Id: {0}", this.Configuration.Id));
            }

            xss.Compile();
            return xss;
        }
    }
}